package com.skstudio.report;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.logging.Logger;
import org.apache.commons.lang3.ArrayUtils;

public class SpyData {

    ReportCpu mReportCpu = new ReportCpu();
    ReportMem mReportMem = new ReportMem();
    ReportNetwork mReportNet = new ReportNetwork();
    ReportProcess mReportProc = new ReportProcess();
    ReportThreads mReportThread = new ReportThreads();
    ReportBattery mReportBat = new ReportBattery();
    ReportWifiConn mReportWifi = new ReportWifiConn();
    ReportCpuSpeed mReportSpeed = new ReportCpuSpeed();
    final static long eptSysUpTime = 3735928559L; //0xdeadbeef
    final static long eptCPU = eptSysUpTime + 1;
    final static long eptProc = eptSysUpTime + 2;
    final static long eptThread = eptSysUpTime + 3;
    final static long eptMem = eptSysUpTime + 4;
    final static long eptNetwork = eptSysUpTime + 5;
    final static long eptWifi = eptSysUpTime + 6;
    final static long eptBattery = eptSysUpTime + 7;
    final static long eptCPUSpeed = eptSysUpTime + 8;

    //ArrayList< byte[] > mDataSerial = new ArrayList< byte[] > ();
    public interface onSerialEventListener {

        public void onEvent(int current, int total);
    }
    RandomAccessFile mRaf = null;
    int mSavedRecord = 0;
    public static final byte[] cFileSync = {0x73, 0x6B, 0x73, 0x74, 0x61, 0x73, 0x6B, 0x73, 0x70, 0x79, 0x0A};
    static final byte[] cSegmentSync = {0x24, 0x53, 0x45, 0x47, 0x4D, 0x45, 0x4E, 0x54, 0x0A};

    @SuppressWarnings("CallToThreadDumpStack")
    public boolean serial(boolean bOutput, StringBuilder err, onSerialEventListener listener) {
        if (bOutput) {
            try {
                // write file sync header only
                mRaf = new RandomAccessFile("taskspy.log", "rwd");
                mRaf.setLength(0);
                mRaf.write(cFileSync);
                byte[] numSegment = ByteBuffer.allocate(4).putInt(0).array();
                mRaf.write(numSegment);

                mSavedRecord = 0;
                return true;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                closeWrite();
            }
            return false;
        } else {
            resetAll();

            FileInputStream fis = null;
            byte[] buffer = null;
            byte[] syncHead = new byte[cFileSync.length];
            byte[] sgmtHead = new byte[cSegmentSync.length];
            byte[] sgmtSize = new byte[4];
            byte[] sgmtCount = new byte[4];
            byte[] procNameSize = new byte[4];

            try {
                fis = new FileInputStream("/sdcard/taskspy.log");
                fis.read(syncHead, 0, cFileSync.length);
                if (!Arrays.equals(cFileSync, syncHead)) {
                    throw new IOException("wrong file");
                }

                fis.read(sgmtCount);
                ByteBuffer bb = ByteBuffer.wrap(sgmtCount);
                int count = bb.getInt();

                for (int ii = 0; ii < count; ii++) {
                    int cb = fis.read(sgmtHead);
                    if (!Arrays.equals(cSegmentSync, sgmtHead)) {
                        throw new IOException("wrong segment");
                    }
                    cb = fis.read(sgmtSize);
                    if (cb < sgmtSize.length) {
                        throw new IOException("bad format");
                    }

                    bb = ByteBuffer.wrap(sgmtSize);
                    int size = bb.getInt();
                    if (size == 0) {
                        throw new IOException("bad format");
                    }

                    buffer = new byte[size];
                    if (fis.read(buffer) != size) {
                        throw new IOException("bad format");
                    }

                    onParseResult(buffer, size);

                    listener.onEvent(ii, count);
                }
                fis.close();
                return true;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                err.append(e.getMessage());
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return false;
        }
    }

    @SuppressWarnings("CallToThreadDumpStack")
    public void onParseResult(byte[] buffer, long total) {
        int offset = 0;
        Logger console = Logger.getLogger("PPP");

        do {
            long type = ReportBase.getCxxUnsignedLong(buffer, offset);
            long size = ReportBase.getCxxUnsignedLong(buffer, offset + 4);
            if (type == 0) {
                break;
            }

            if (type == eptCPU) {
                mReportCpu.parse(buffer, offset + 8);
            } else if (type == eptProc) {
                mReportProc.parse(buffer, offset + 8);
            } else if (type == eptThread) {
                mReportThread.parse(buffer, offset + 8);
            } else if (type == eptMem) {
                mReportMem.parse(buffer, offset + 8);
            } else if (type == eptNetwork) {
                mReportNet.parse(buffer, offset + 8);
            } else if (type == eptWifi) {
                mReportWifi.parse(buffer, offset + 8);
            } else if (type == eptBattery) {
                mReportBat.parse(buffer, offset + 8);
            } else if (type == eptCPUSpeed) {
                mReportSpeed.parse(buffer, offset + 8);
            }

            offset += 8 + size;
        } while (offset < total);

        ReportBase.mRecordCount++;
    }

    public byte[] packBattery(int level, int temp, int volt) {
        byte[] buffer = new byte[20];
        ReportBase.setCxxUnsignedLong(buffer, 0, eptBattery);
        ReportBase.setCxxUnsignedLong(buffer, 4, 12);
        ReportBase.setCxxUnsignedLong(buffer, 8, level);
        ReportBase.setCxxUnsignedLong(buffer, 12, temp);
        ReportBase.setCxxUnsignedLong(buffer, 16, volt);
        return buffer;
    }

    public byte[] packWifi(int level) {
        byte[] buffer = new byte[12];
        ReportBase.setCxxUnsignedLong(buffer, 0, eptWifi);
        ReportBase.setCxxUnsignedLong(buffer, 4, 4);
        ReportBase.setCxxUnsignedLong(buffer, 8, level);
        return buffer;
    }

    public ReportWifiConn getReportWifiConn() {
        return mReportWifi;
    }

    public ReportBattery getReportBattery() {
        return mReportBat;
    }

    public ReportProcess getReportProcess() {
        return mReportProc;
    }

    public ReportThreads getReportThreads() {
        return mReportThread;
    }

    public ReportCpu getReportCpu() {
        return mReportCpu;
    }

    public ReportMem getReportMem() {
        return mReportMem;
    }

    public ReportNetwork getReportNet() {
        return mReportNet;
    }

    public ReportCpuSpeed getReportCpuSpeed() {
        return mReportSpeed;
    }

    public void resetAll() {
        ReportBase.mRecordCount = 0;

        mReportCpu.reset();
        mReportMem.reset();
        mReportNet.reset();
        mReportProc.reset();
        mReportThread.reset();
        mReportBat.reset();
        mReportWifi.reset();
        mReportSpeed.reset();
        System.gc();
    }

    public void beginWrite() {
        closeWrite();
        serial(true, null, null);
    }

    @SuppressWarnings("CallToThreadDumpStack")
    public void endWrite() {
        if (mRaf != null) {
            try {
                mRaf.seek(cFileSync.length);

                byte[] numSegment = ByteBuffer.allocate(4).putInt(ReportBase.mRecordCount).array();
                mRaf.write(numSegment);

                mRaf.close();
                mRaf = null;
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                closeWrite();
            }
        }
    }

    @SuppressWarnings("CallToThreadDumpStack")
    void closeWrite() {
        if (mRaf != null) {
            try {
                mRaf.close();
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
        mRaf = null;
    }

    public void segmentWrite(byte[] buffer, long total) {
        if (mRaf != null) {
            try {
                mRaf.write(cSegmentSync);
                byte[] sizeSegment = ByteBuffer.allocate(4).putInt((int) total).array();
                mRaf.write(sizeSegment);
                byte[] segment = ArrayUtils.subarray(buffer, 0, (int) total);
                mRaf.write(segment);
                mSavedRecord++;
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                closeWrite();
            }
        }
    }

    public int beginRead(InputStream fis) throws IOException {
        byte[] buffer = null;
        byte[] syncHead = new byte[cFileSync.length];
        byte[] sgmtCount = new byte[4];

        fis.read(syncHead, 0, cFileSync.length);
        if (!Arrays.equals(cFileSync, syncHead)) {
            throw new IOException("wrong file");
        }

        fis.read(sgmtCount);
        ByteBuffer bb = ByteBuffer.wrap(sgmtCount);
        int count = bb.getInt();

        return count;
    }

    public byte[] segmentRead(InputStream fis) throws IOException {
        byte[] sgmtHead = new byte[cSegmentSync.length];
        byte[] sgmtSize = new byte[4];

        int cb = fis.read(sgmtHead);
        if (!Arrays.equals(cSegmentSync, sgmtHead)) {
            throw new IOException("wrong segment");
        }
        cb = fis.read(sgmtSize);
        if (cb < sgmtSize.length) {
            throw new IOException("bad format");
        }

        ByteBuffer bb = ByteBuffer.wrap(sgmtSize);
        int size = bb.getInt();
        if (size == 0) {
            throw new IOException("bad format");
        }

        byte[] buffer = new byte[size];
        if (fis.read(buffer) != size) {
            throw new IOException("bad format");
        }

        return buffer;
    }
}
